本次目標
Graceful shutdown 可以稱優雅關閉,是指應用程式在接收到 SIGTERM 訊號後,能有有條理的結束應用程式運作,而不是直接被強制終止。這讓應用程式可以在關閉前完成正在進行的工作,保存重要的狀態資訊,並釋放佔用的系統資源等。
為什麼 graceful shutdown 這麼重要?
回到 Kubernetes 上,想一下刪除 Pod 時會發生什麼事 ? 如果還記得上一篇所提到的話,Endpoint 物件會移除該 Pod IP。也會觸發 kube-proxy、Ingress controller、DNS、service mesh 等所有事件,這些動作將會停止流量被路由到要被停止的 IP。
這過程元件並非即時的操作,因此無法保證需要多長時間才能將 IP 從其內部狀態中刪除。Pod 狀態也會被更新成 Terminate
。而 kubelet 將會進行
如果從 kube-proxy
或 Ingress controller
中刪除端點(Pod IP)之前終止 Pod,將可能會遇到停機。即 Kubernetes 仍將流量路由到該 IP,但 Pod 已不存在。
要避免需透過等待,當 Pod 即將刪除時,它會收到 SIGTERM
訊號。此時應用程式可以捕獲該訊號並開始關閉。
端點不太可能立即從 Kubernetes 中的所有元件(Ingress controller, kube-proxy, CoreDNS etc.)中即時刪除。因此可以
SIGTERM
,仍處理傳入流量預設情況下,Kubernetes 將發送 SIGTERM
訊號並等待 30 秒(Pod.spec.terminationGracePeriodSeconds),然後再強制終止進程。這 30 秒過程,前幾秒會傳播端點移除的訊息給 kube-proxy
或 Ingress controller
等資源。這過程還會持續接收流量只不過是越來越少,直到沒有,當沒有流量時,可以安全關閉與資料庫的連線或任何持久連線並終止進程。
如果 30 秒內其中 15 秒無法把元件關聯的端點移除則時間就會被往後延長。當到 30 秒後,Pod 將會被強制移除,30 秒為 Pod.spec.terminationGracePeriodSeconds
預設是可以異動。
如果舊有架構無法更改程式碼來接收 SIGTERM
並等待更長時間怎麼辦?可以使用腳本方式睡眠等待固定的時間,然後讓應用程式退出。在 Pod 中可以使用 preStop
這個關鍵欄位。
preStop hook
是 Pod LifeCycle Hook 之一。在執行 SIGTERM
之前,Kubernetes 在 Pod 中有 preStop hook
,可以設定 15 秒等待
# 官方範例
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
preStop:
exec:
command: ["sleep","15"]
在應用程式中使用 preStop hook 和等待 15 秒是不同的。在將 SIGTERM
分派到應用程式之前呼叫 preStop hook,因此 preStop 運行時,應用程式並不知道它即將關閉。另一方面 preStop hook 中的延遲是 terminationGracePeriodSeconds
中 30 秒的一部分。
如果 preStop hook
等待 25 秒,一旦完成,容器收到 SIGTERM
。它剩下 5 秒的時間終止,否則 kubelet 將發出 SIGKILL
。另一方面是 preStop hook
大於 terminationGracePeriodSeconds
時 kubelet
將發送 SIGKILL
訊號並強制終止容器,過程中不會調度 SIGTERM
訊號。如果需要更長延遲調整 terminationGracePeriodSeconds
是對的。
preHook
的設定會取決於,如果應用程式中正常處理關閉,時間可能會要 60 秒甚至更短,相反如果應用程式必須在關閉前刷新日誌或指標,則可能需要更長的時間間隔。下圖為一個執行時序圖
From https://learnk8s.io/graceful-shutdown#how-to-gracefully-shut-down-pods
如果應用程式提供長期連線(WebSocket),不希望在 30 秒內終止它。理想上是用戶端終止,讓連線正常中斷。同樣,如果要對大型影片進行轉碼,不希望滾動更新時刪除當前的 Pod(以及工作時間)。
如何避免延遲關閉 Pod?
加大 terminationGracePeriodSeconds
時間,希望能夠連線被消費完或作業完成。但存在一些缺點
kubelet
不會檢查 liveness probe。例如,如果進程卡住了,它不會重新啟動它應該考慮為每個版本建立新的部署,而不是增加時間。當建立全新的 Deployment
時,現有的 Deployment
保持不變,同時長時間運行的作業可以像往常一樣繼續處理,並且長期連接也保持不變。
部分內容是翻譯至參考[2],工作上確實有發生此問題因此有特別留上述筆記向上報告。
實務上在整合 celery worker 時,確實存在問題。當沒特別設定 terminationGracePeriodSeconds
時如果 Pod 被縮減,就發生任務卡住且無法復原情況,因此為了短暫避免此問題發生,就加大了時間至 3600 秒。但這理論上不是最佳解,反而是要往 Job 方向去思考是否能解決這件事。
下個章節將透過 Quarkus 實際實現。